package pl.edu.pw.ee.buildcity.dao.jpa;

import org.apache.log4j.Logger;
import org.springframework.transaction.annotation.Transactional;
import pl.edu.pw.ee.buildcity.dao.GenericDao;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * @author mgrzechocinski
 * @param <T>
 * @since 2009-09-20
 */
public class JPAGenericDao<T, PK extends Serializable> implements GenericDao<T, PK> {

	private static final Logger log = Logger.getLogger(JPAGenericDao.class);

	@PersistenceContext(unitName = "buildcityPU")
	private EntityManager em;
	private static final int ALL_ROWS = -1;

	@Override
	@Transactional
	public T persist(T entityToPersist) {
		em.persist(entityToPersist);
		return entityToPersist;
	}

	@Override
	@Transactional
	public T merge(T entityToPersist) {
		T merged = em.merge(entityToPersist);
		return merged;
	}

	@Override
	@Transactional
	public void remove(T objectToBeDeleted) {
		em.remove(em.merge(objectToBeDeleted));
	}

	@Override
	public List<T> executeNamedQuery(String queryName) {
		return executeNamedQuery(queryName, new HashMap<String, Object>());
	}

	@Override
	public List<T> executeNamedQuery(String queryName, Map<String, Object> params) {
		return executeNamedQuery(queryName, params, ALL_ROWS);
	}

	@Override
	public List<T> executeNamedQuery(String queryName, Map<String, Object> params, int maxResults) {
		Query query = em.createNamedQuery(queryName);
		if (maxResults != ALL_ROWS) {
			query.setMaxResults(maxResults);
		}
		for (Entry<String, Object> entry : params.entrySet()) {
			query.setParameter(entry.getKey(), entry.getValue());
		}
		List resultList = query.getResultList();
		if (resultList.isEmpty()) {
			log.warn("Could not find entity with " + Arrays.deepToString(params.values().toArray()));
			throw new EntityNotFoundException();
		}
		return resultList;
	}

	@Override
	public T getSingleResultOfNamedQuery(String queryName) throws EntityNotFoundException {
		return getSingleResultOfNamedQuery(queryName, new HashMap<String, Object>());
	}

	@Override
	public T getSingleResultOfNamedQuery(String queryName, Map<String, Object> params) throws EntityNotFoundException {
		List<T> foundEntities = executeNamedQuery(queryName, params);
		if (foundEntities.size() > 1) {
			throw new NonUniqueResultException();
		}
		return foundEntities.get(0);
	}

	@Override
	public List<T> executeQuery(String jpaqlQuery) {
		return em.createQuery(jpaqlQuery).getResultList();
	}

	@Override
	public List<T> executeQuery(String jpaqlQuery, Map<String, Object> params) {
		Query query = em.createQuery(jpaqlQuery);
		for (Entry<String, Object> entry : params.entrySet()) {
			query.setParameter(entry.getKey(), entry.getValue());
		}
		return query.getResultList();
	}

	@Override
	public T getSingleResultOfQuery(String jpaqlQuery) throws EntityNotFoundException {
		return getSingleResultOfQuery(jpaqlQuery, new HashMap<String, Object>());
	}

	@Override
	public T getSingleResultOfQuery(String jpaqlQuery, Map<String, Object> params) {
		List<T> foundEntities = executeQuery(jpaqlQuery, params);
		if (foundEntities.isEmpty()) {
			log.warn("Could not find entity with " + Arrays.deepToString(params.values().toArray()));
			throw new EntityNotFoundException();
		}
		return foundEntities.get(0);
	}

	@Override
	public T getById(Class<T> entity, PK key) {
		return em.find(entity, key);
	}
}
